//
//  NSGIF.m
//
//  Created by Sebastian Dobrincu
//

#import "NSGIF.h"

@implementation NSGIF

// Declare constants
#define fileName     @"NSGIF"
#define timeInterval @(600)
#define tolerance    @(0.01)

typedef NS_ENUM(NSInteger, GIFSize) {
	GIFSizeVeryLow  = 2,
	GIFSizeLow      = 3,
	GIFSizeMedium   = 5,
	GIFSizeHigh     = 7,
	GIFSizeOriginal = 10
};

#pragma mark - Public methods

+ (void)optimalGIFfromURL:(NSURL*)videoURL loopCount:(int)loopCount completion:(void(^)(NSURL *GifURL))completionBlock {
	
	float delayTime = 0.02f;
	
	// Create properties dictionaries
	NSDictionary *fileProperties = [self filePropertiesWithLoopCount:loopCount];
	NSDictionary *frameProperties = [self framePropertiesWithDelayTime:delayTime];
	
	AVURLAsset *asset = [AVURLAsset assetWithURL:videoURL];
	
	float videoWidth = [[[asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0] naturalSize].width;
	float videoHeight = [[[asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0] naturalSize].height;
	
	GIFSize optimalSize = GIFSizeMedium;
	if (videoWidth >= 1200 || videoHeight >= 1200)
		optimalSize = GIFSizeVeryLow;
	else if (videoWidth >= 800 || videoHeight >= 800)
		optimalSize = GIFSizeLow;
	else if (videoWidth >= 400 || videoHeight >= 400)
		optimalSize = GIFSizeMedium;
	else if (videoWidth < 400|| videoHeight < 400)
		optimalSize = GIFSizeHigh;
	
	// Get the length of the video in seconds
	float videoLength = (float)asset.duration.value/asset.duration.timescale;
	int framesPerSecond = 4;
	int frameCount = videoLength*framesPerSecond;
	
	// How far along the video track we want to move, in seconds.
	float increment = (float)videoLength/frameCount;
	
	// Add frames to the buffer
	NSMutableArray *timePoints = [NSMutableArray array];
	for (int currentFrame = 0; currentFrame<frameCount; ++currentFrame) {
		float seconds = (float)increment * currentFrame;
		CMTime time = CMTimeMakeWithSeconds(seconds, [timeInterval intValue]);
		[timePoints addObject:[NSValue valueWithCMTime:time]];
	}
	
	// Prepare group for firing completion block
	dispatch_group_t gifQueue = dispatch_group_create();
	dispatch_group_enter(gifQueue);
	
	__block NSURL *gifURL;
	dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
		
		gifURL = [self createGIFforTimePoints:timePoints fromURL:videoURL fileProperties:fileProperties frameProperties:frameProperties frameCount:frameCount gifSize:optimalSize];
		
		dispatch_group_leave(gifQueue);
	});
	
	dispatch_group_notify(gifQueue, dispatch_get_main_queue(), ^{
		// Return GIF URL
		completionBlock(gifURL);
	});
	
}

+ (void)createGIFfromURL:(NSURL*)videoURL withFrameCount:(int)frameCount delayTime:(float)delayTime loopCount:(int)loopCount completion:(void(^)(NSURL *GifURL))completionBlock {
	
	// Convert the video at the given URL to a GIF, and return the GIF's URL if it was created.
	// The frames are spaced evenly over the video, and each has the same duration.
	// delayTime is the amount of time for each frame in the GIF.
	// loopCount is the number of times the GIF will repeat. Defaults to 0, which means repeat infinitely.
	
	// Create properties dictionaries
	NSDictionary *fileProperties = [self filePropertiesWithLoopCount:loopCount];
	NSDictionary *frameProperties = [self framePropertiesWithDelayTime:delayTime];
	
	AVURLAsset *asset = [AVURLAsset assetWithURL:videoURL];
	
	// Get the length of the video in seconds
	float videoLength = (float)asset.duration.value/asset.duration.timescale;
	
	// How far along the video track we want to move, in seconds.
	float increment = (float)videoLength/frameCount;
	
	// Add frames to the buffer
	NSMutableArray *timePoints = [NSMutableArray array];
	for (int currentFrame = 0; currentFrame<frameCount; ++currentFrame) {
		float seconds = (float)increment * currentFrame;
		CMTime time = CMTimeMakeWithSeconds(seconds, [timeInterval intValue]);
		[timePoints addObject:[NSValue valueWithCMTime:time]];
	}
	
	// Prepare group for firing completion block
	dispatch_group_t gifQueue = dispatch_group_create();
	dispatch_group_enter(gifQueue);
	
	__block NSURL *gifURL;
	dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
		
		gifURL = [self createGIFforTimePoints:timePoints fromURL:videoURL fileProperties:fileProperties frameProperties:frameProperties frameCount:frameCount gifSize:GIFSizeMedium];
		
		dispatch_group_leave(gifQueue);
	});
	
	dispatch_group_notify(gifQueue, dispatch_get_main_queue(), ^{
		// Return GIF URL
		completionBlock(gifURL);
	});
	
}

#pragma mark - Base methods

+ (NSURL *)createGIFforTimePoints:(NSArray *)timePoints fromURL:(NSURL *)url fileProperties:(NSDictionary *)fileProperties frameProperties:(NSDictionary *)frameProperties frameCount:(int)frameCount gifSize:(GIFSize)gifSize{
	
	NSString *timeEncodedFileName = [NSString stringWithFormat:@"%@-%lu.gif", fileName, (unsigned long)([[NSDate date] timeIntervalSince1970]*10.0)];
	NSString *temporaryFile = [NSTemporaryDirectory() stringByAppendingString:timeEncodedFileName];
	NSURL *fileURL = [NSURL fileURLWithPath:temporaryFile];
	if (fileURL == nil)
		return nil;
	
	CGImageDestinationRef destination = CGImageDestinationCreateWithURL((__bridge CFURLRef)fileURL, kUTTypeGIF , frameCount, NULL);
	
	
	AVURLAsset *asset = [AVURLAsset URLAssetWithURL:url options:nil];
	AVAssetImageGenerator *generator = [AVAssetImageGenerator assetImageGeneratorWithAsset:asset];
	generator.appliesPreferredTrackTransform = YES;
	
	CMTime tol = CMTimeMakeWithSeconds([tolerance floatValue], [timeInterval intValue]);
	generator.requestedTimeToleranceBefore = tol;
	generator.requestedTimeToleranceAfter = tol;
	
	NSError *error = nil;
	CGImageRef previousImageRefCopy = nil;
	for (NSValue *time in timePoints) {
		CGImageRef imageRef;
		
#if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR
		imageRef = (float)gifSize/10 != 1 ? createImageWithScale([generator copyCGImageAtTime:[time CMTimeValue] actualTime:nil error:&error], (float)gifSize/10) : [generator copyCGImageAtTime:[time CMTimeValue] actualTime:nil error:&error];
#elif TARGET_OS_MAC
		imageRef = [generator copyCGImageAtTime:[time CMTimeValue] actualTime:nil error:&error];
#endif
		
		if (error) {
			NSLog(@"Error copying image: %@", error);
		}
		if (imageRef) {
			CGImageRelease(previousImageRefCopy);
			previousImageRefCopy = CGImageCreateCopy(imageRef);
		} else if (previousImageRefCopy) {
			imageRef = CGImageCreateCopy(previousImageRefCopy);
		} else {
			NSLog(@"Error copying image and no previous frames to duplicate");
			return nil;
		}
		CGImageDestinationAddImage(destination, imageRef, (CFDictionaryRef)frameProperties);
		CGImageRelease(imageRef);
	}
	CGImageRelease(previousImageRefCopy);
	
	CGImageDestinationSetProperties(destination, (CFDictionaryRef)fileProperties);
	// Finalize the GIF
	if (!CGImageDestinationFinalize(destination)) {
		NSLog(@"Failed to finalize GIF destination: %@", error);
		if (destination != nil) {
			CFRelease(destination);
		}
		return nil;
	}
	CFRelease(destination);
	
	return fileURL;
}

#pragma mark - Helpers

CGImageRef createImageWithScale(CGImageRef imageRef, float scale) {
	
#if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR
	CGSize newSize = CGSizeMake(CGImageGetWidth(imageRef)*scale, CGImageGetHeight(imageRef)*scale);
	CGRect newRect = CGRectIntegral(CGRectMake(0, 0, newSize.width, newSize.height));
	
	UIGraphicsBeginImageContextWithOptions(newSize, NO, 0);
	CGContextRef context = UIGraphicsGetCurrentContext();
	if (!context) {
		return nil;
	}
	
	// Set the quality level to use when rescaling
	CGContextSetInterpolationQuality(context, kCGInterpolationHigh);
	CGAffineTransform flipVertical = CGAffineTransformMake(1, 0, 0, -1, 0, newSize.height);
	
	CGContextConcatCTM(context, flipVertical);
	// Draw into the context; this scales the image
	CGContextDrawImage(context, newRect, imageRef);
	
	//Release old image
	CFRelease(imageRef);
	// Get the resized image from the context and a UIImage
	imageRef = CGBitmapContextCreateImage(context);
	
	UIGraphicsEndImageContext();
#endif
	
	return imageRef;
}

#pragma mark - Properties

+ (NSDictionary *)filePropertiesWithLoopCount:(int)loopCount {
	return @{(NSString *)kCGImagePropertyGIFDictionary:
				 @{(NSString *)kCGImagePropertyGIFLoopCount: @(loopCount)}
			 };
}

+ (NSDictionary *)framePropertiesWithDelayTime:(float)delayTime {
	
	return @{(NSString *)kCGImagePropertyGIFDictionary:
				 @{(NSString *)kCGImagePropertyGIFDelayTime: @(delayTime)},
			 (NSString *)kCGImagePropertyColorModel:(NSString *)kCGImagePropertyColorModelRGB
			 };
}

@end
